函式(function)可以用在計算後得到某個值,也可以用於定義某個任務(功能)。
關於定義函式的幾種方式,讓我們繼續接著往下看
以下有幾種方式可以定義函式,而每一種方式都有自己對應的稱呼方式。
在學習函式陳述式(function statements)之前,要先來了解關於陳述式的意思。
陳述式就像句子,而我們透過執行句中的設定,使得某些事情發生。
如同下方的程式碼,我們宣告了變數 a
以及設定了一個 if
條件句,而這每一行都是一個陳述式,也就是剛剛提到的句子,這些句子會決定程式要執行的行為。
var a = 3;
if(a > 5){...}
將上述所提的概念用在函式上就成了函式陳述式,如下:
這個函式陳述式說明了如果執行時,會得到 Hello!
的值。
function sayHi(){
console.log('Hello!');
}
看完函式陳述式怎麼宣告,接下來要看看怎麼使用它
其實使用的方式也很簡單,透過 函式名稱()
這樣就代表我們想要執行這個函式中我們定義的程式碼內容。
拿上面的例子來測試,會得到:
function sayHi(){
console.log('Hello!');
}
sayHi();
當然我們也可以透過傳遞參數,讓這個函式能有更多的運用:
function sayHi(person){
console.log(`Hello! ${person}`);
}
sayHi('Bill');
將 Bill
傳入到函式中,函式的內容就會找到與參數相同的名稱 person
並將 Bill
的值透過 person
給 console.log(...);
中的 person
使用
再來看看兩個測試例子:
var a = 3;
function test(a){
a = 5;
console.log(`在 test 函式中 a 的值為: ${a}`);
}
test(a);
console.log(`在全域環境中 a 的值為: ${a}`);
因為 傳值(call by value) 的關係,所以即使傳入到函式中的 變數 a
的值被更改,在全域的 a
也不會因此而被更改(兩者處於不同的記憶體空間)。
var person = { name: "Bill" };
function test(person){
person.name = "Jack";
console.log(`在 test 函式中特性 name 的值為: ${person.name}`);
}
test(person);
console.log(`在全域環境中特性 name 的值為: ${person.name}`);
因為 傳參考(call by reference) 的緣故,傳入到函式中的 person
物件當特性值被更改時會導致在全域的 person
物件也被更改(兩者指向同一個記憶體位址)。
函式表達式(function expression) 可以將函式儲存在變數中,而這樣子的函式可以不需要函式名稱,也稱為匿名函式。使用方式如下:
var totalSum = function(number,number2){
return number + number2;
}
console.log(totalSum);
console.log(totalSum(1,2));
從第一個結果可以看到變數 totalSum
儲存了一個匿名函式,所以當執行 變數名稱()
就可以取得這個變數儲存的函式運算後的值。
而這個值我們必須透過 return
的方式將值傳回 totalSum
。
當用於遞迴時,這個匿名函式就可以有函式名稱。
如下方儲存於result
變數中的 factorial
函式。
當執行 result(5)
時就可以得到值 120
var result = function factorial(number){
if(number === 1)
return number;
return number * factorial(number - 1);
}
result(5);
函式建構式可以用於建立物件原型(也稱為模板),關於建立時有一些可以先知道的部分:
prototype
建立方法而不應該直接寫入到模板中接下來看看如何建立模板:
function Person(name,age,habbit){
this.name = name;
this.age = age;
this.habbit = habbit;
}
const personA = new Person('Bill',23,'Read books');
console.log(personA);
const personB = new Person('Jack',30,'Play tennis');
console.log(personB);
首先先定義好模板 Person
,並新增這個模板需要的特性。
之後透過 new
關鍵字的方式建立物件,這邊需要注意的是 this
會指向透過 new
建立的物件。
而透過建立的模板,可以建立多個有相同特性的不同物件。
那如果我們需要新增一個方法給這個模板呢?
這裡可能會覺得為什麼不直接於一開始建立模板時就將方法也新增進去
在 Understanding the Weird Parts 中的函數建構子與「.prototype」章節有提到:
這麼做會有個問題,那就是造成太多的記憶體空間被佔據。
來看看測試的例子:
先將方法直接在模板中建立
function Person(name,age,habbit){
this.name = name;
this.age = age;
this.habbit = habbit;
this.sayHi = function(){
console.log("Hi");
};
}
const personA = new Person('Bill',23,'Read books');
console.log(personA);
const personB = new Person('Jack',30,'Play tennis');
console.log(personB);
可以看到每一個物件都從模板中繼承了 sayHi
這個方法,
但是 sayHi
方法可以被重複使用,二來是這樣的寫法會造成記憶體需要多保留這些重複建立的方法
也就因此造成了多餘的記憶體空間的浪費了
所以更好的方式是額外透過 prototype
語法將方法新增到模板,也就是這個原型物件上:
function Person(name,age,habbit){
this.name = name;
this.age = age;
this.habbit = habbit;
}
const personA = new Person('Bill',23,'Read books');
const personB = new Person('Jack',30,'Play tennis');
Person.prototype.sayHi = function(){
return "Hi";
}
console.log(personA);
console.log(personB);
console.log(personB.sayHi());
console.log(Person.prototype);
從圖中可以看到,新建立的 personA
、personB
物件並沒有 sayHi
這個方法,但是可以在 Person
模板
中看到sayHi
這個方法,如此一來就能讓每個物件一樣能夠使用 sayHi
,但是記憶體卻只需要保留一個空間給它。
立即函式,顧名思義就是會馬上執行的函式。
JavaScript 引擎看到立即函式就會立刻編譯這個 function。
而立即函式透過在函式外用 ()
包住整個函式,可以達到避免裡面的變數污染到全域環境變數的效用。
來看個測試例子:
(function(){
var a = 3;
console.log(3);
})()
如同前面對於立即函式的描述,所以會馬上得到結果為 3
的值。
arguments 物件是一個對應傳入函式之引數的類陣列(Array-like)物件。
Arguments 物件
關於類陣列(array-like)物件, MDN 上有提到:
物件具有 length 屬性以及索引化(indexed)的元素
所以如果需要透過陣列的方式操作,則需要轉為陣列才可以。
來看看測試例子
const test = function(a,b,c){
console.log(arguments);
};
test(5,10,15);
可以看到 arguments
物件除了將傳入的值存於一個類陣列(array-like)中,還包含了其他的方法(如 callee
等)。
所以如果需要透過陣列的方式操作的話則必須還要做些處理,而下列程式碼是一種方式:
const test = function(a,b,c){
const ary = Array.prototype.slice.call(arguments);
console.log(ary);
};
test(5,10,15);
透過 Array.prototype.slice.call()
可以將 arguments
物件轉為陣列型別。
關於函式的學習今天先告一個段落囉,明天來學習ES6的箭頭函式(arrow function)
明天見~